﻿namespace NJQuery.MaskedInputExtension {
	using System;
	using System.Collections.Specialized;
	using System.ComponentModel;
	using System.Globalization;
	using System.Web.UI;
	using System.Text;
	using System.Diagnostics.CodeAnalysis;

	/// <summary>
	/// Server control used to reference the <see href="http://digitalbush.com/projects/masked-input-plugin/">masked input plugin</see>.
	/// </summary>
	[ParseChildren(true), PersistChildren(false), ValidationProperty("Text")]
	public class MaskedInput : ManagedWebControl, IPostBackDataHandler, ITextControl {


		#region Fields

		private bool focused;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="MaskedInput"/> class.
		/// </summary>
		public MaskedInput() : base(HtmlTextWriterTag.Input) { }

		#endregion


		#region Properties: Widget initialization options

		[DefaultValue(""), Category("Behavior")]
		public virtual string Mask {
			get { return ViewState["Mask"] as string ?? string.Empty; }
			set { ViewState["Mask"] = value; }
		}

		#endregion


		#region Properties: Additional

		/// <summary>
		/// Gets or sets the value of the text in the input control.
		/// </summary>
		[DefaultValue(""), Category("Data")]  //TODO: Appearance/Data ??
		public virtual string Text {
			get { return ViewState["Text"] as string; }
			set { ViewState["Text"] = value; }
		}

		/// <summary>
		/// Gets or set whether the user is able to modify the value in the input control.
		/// </summary>
		[DefaultValue(false), Category("Behavior")]
		[SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "ReadOnly", Justification = "Matches property used by System.Web.UI.WebControls.TextBox.")]
		public virtual bool ReadOnly {
			get { return ViewState["ReadOnly"] as bool? ?? false; }
			set { ViewState["ReadOnly"] = value; }
		}

		#endregion


		#region Events

		/// <summary>
		/// Occurs on postback when the <see cref="Text"/> property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler TextChanged;

		#endregion


		#region Methods: Event hooks

		/// <summary>
		/// Raises the <see cref="TextChanged"/> event.
		/// </summary>
		/// <param name="e">The <see cref="EventArgs"/> object that contains the event data.</param>
		protected virtual void OnTextChanged(EventArgs e) {
			if(TextChanged != null) TextChanged.Invoke(this, e);
		}

		#endregion


		#region Methods: Rendering hooks

		public override void Focus() {
			if(Manager.GetCurrent(Page).IsInAsyncPostBack)
				base.Focus();
			else
				focused = true;
		}

		/// <summary>
		/// Raises the <see cref="Control.PreRender"/> event.
		/// </summary>
		/// <param name="e">The <see cref="EventArgs"/> object containing the event data.</param>
		protected override void OnPreRender(EventArgs e) {
			base.OnPreRender(e);

			if(Visible) Page.RegisterRequiresPostBack(this);
		}

		/// <summary>
		/// Adds HTML attributes and styles that need to be rendered to the specified <see cref="HtmlTextWriter"/>.
		/// This property is used primarily by control developers.
		/// </summary>
		/// <param name="writer">The <see cref="HtmlTextWriter"/> object that receives the rendered content.</param>
		protected override void AddAttributesToRender(HtmlTextWriter writer) {

			Page.ClientScript.RegisterForEventValidation(UniqueID);

			writer.AddAttribute(HtmlTextWriterAttribute.Name, UniqueID);
			writer.AddAttribute(HtmlTextWriterAttribute.Type, "text");
			writer.AddAttribute(HtmlTextWriterAttribute.Value, Text);

			base.AddAttributesToRender(writer);

			if(ReadOnly) writer.AddAttribute(HtmlTextWriterAttribute.ReadOnly, "readonly");
			writer.AddAttribute(HtmlTextWriterAttribute.AutoComplete, "off");
		}

		/// <summary>
		/// Allows a jQuery server control to reference scripts and stylesheets necessary to render their client markup and widgets.
		/// </summary>
		/// <param name="manager">The <see cref="Manager"/> used to handle style and script referencing in addition to unified generation of load and ready scripts.</param>
		protected override void Register(Manager manager) {

			manager.RegisterClientScript(NJQuery.ClientScript.Core);
			manager.RegisterClientScript(ClientScript.MaskedInputPlugin);

			manager.RegisterForReadyScript(this);
		}

		/// <summary>
		/// Renders the jQuery "Ready Event" script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A <see cref="ScriptBuilder"/> that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderReadyScript(ScriptBuilder script) {
			RenderInitScript(script);
		}

		/// <summary>
		/// Renders the jQuery UI initialization script.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A <see cref="ScriptBuilder"/> that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderInitScript(ScriptBuilder script) {

			script.NewLine();

			script.RenderIdSelector(ClientID);
			script.Append(".mask(");

			script.AppendValue(Mask);

			PropertyCollection properties = script.CreateProperties();
			AddInitProperties(properties);
			if(properties.Count != 0) {
				script.Append(", ");
				script.RenderProperties(properties);
			}

			script.Append(")");
			if(focused) script.Append(".focus()");
			script.Append(";");

		}

		/// <summary>
		/// Creates the properties object that will be rendered as the jQuery UI initialization options.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="properties">A <see cref="PropertyCollection"/> object that will become the jQuery UI initialization options.</param>
		protected virtual void AddInitProperties(PropertyCollection properties) {

		}

		#endregion


		#region Methods: Post data/events

		/// <summary>
		/// Processes postback data for an ASP.NET server control.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="postDataKey">The key identifier for the control.</param>
		/// <param name="postCollection">The collection of all incoming name values.</param>
		/// <returns>True if the server control's state changes as a result of the postback; otherwise, false.</returns>
		protected virtual bool LoadPostData(string postDataKey, NameValueCollection postCollection) {

			Page.ClientScript.ValidateEvent(postDataKey);

			string text = Text;
			string newText = postCollection[postDataKey];

			if(newText == text) return false;

			Text = newText;
			return true;
		}

		/// <summary>
		/// Signals the server control to notify the ASP.NET application that the state of the control has changed.
		/// This method is used primarily by control developers.
		/// </summary>
		[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "This method is amazing.  You get a complaint if you don't include the protected one, but when you do, it tells you to use events.  I included it simply because it gives the derrived control a chance to hook deeper.")]
		protected virtual void RaisePostDataChangedEvent() {
			OnTextChanged(EventArgs.Empty);
		}

		#endregion


		#region Explicit Implementation: IPostBackDataHandler

		bool IPostBackDataHandler.LoadPostData(string postDataKey, NameValueCollection postCollection) {
			return LoadPostData(postDataKey, postCollection);
		}

		void IPostBackDataHandler.RaisePostDataChangedEvent() {
			RaisePostDataChangedEvent();
		}

		#endregion


	}

}